/** 命令行参数解析类, 内部调用getopt.h实现
 * @file argparse.hpp
 * @author Kiven Lee
 * @version 1.0
 * @date 2021-08-26
 *
 * @copyright Kivensoft (c) 2018 - 2021
 *
 */
#pragma once

#include <string>
#include <string_view>
#include <type_traits>
#include <list>
#include <memory>

namespace klib {

    struct arg_option {
        enum OptFlag: uint8_t { NoArg, HasArg, Optional, Required, HasMore, MustMore };

        char        sopt; // 短选项名称, '\0': 没有短选项
        const char* lopt; // 长选项/命令参数名称, 必填项
        OptFlag     flag; // 选项标志，当对象是选项时表明有参数或无参数(NoArg/HasArg), 是命令参数是：可选/必选/0-N/1-N
        const char* def;  // 选项缺省值
        const char* desc; // 选项描述文本
        char*       val;  // 解析后得到的选项值, 无参数类型非空表示已设置，有参数类型指向具体的参数
    };

    class arg_parse {

    private:
        arg_option* opts;
        int         cmdarg_index;

    public:
        arg_parse(arg_option* opts): opts(opts), cmdarg_index(0) {}

        // template<typename T>
        // typename std::enable_if<std::is_same<bool, T>::value
        //                         || std::is_same<int64_t, T>::value
        //                         || std::is_same<const char*, T>::value, void>::type
        // add(const char* name, char shortOpt, const char* longOpt, bool hasArg, T val, const char* desc);

        // 输出使用帮助
        void usage(const char* title, const char* app) const {
            char buf[256], *p;

            if (title) {
                std::fputs(title, stdout);
                std::putc('\n', stdout);
                std::putc('\n', stdout);
            }

            std::fwrite("Usage: \x1b[32m", 1, 12, stdout);
            std::fwrite(app, 1, std::strlen(app), stdout);
            std::fwrite("\x1b[0m", 1, 4, stdout);
            std::fwrite(" [Option]... ", 1, 13, stdout);

            // 输出命令参数详情
            for (arg_option* opt = this->opts; opt->lopt != nullptr; opt++) {
                if (!is_option(opt->flag)) {
                    p = strcopy(buf + 1, opt->lopt);

                    if (opt->flag == arg_option::Optional || opt->flag == arg_option::HasMore) {
                        buf[0] = '['; *p++ = ']';
                    } else {
                        buf[0] = '<'; *p++ = '>';
                    }
                    if (opt->flag == arg_option::HasMore || opt->flag == arg_option::MustMore) {
                        *p++ = '.'; *p++ = '.'; *p++ = '.';
                    }

                    std::fwrite(buf, 1, p - buf, stdout);
                }
            }
            std::putc('\n', stdout);

            // 输出选项详情
            std::fwrite("\nOptoins\n", 1, 10, stdout);
            for (arg_option* opt = this->opts; opt->lopt != nullptr; opt++) {
                if (is_option(opt->flag)) {
                    std::memset(buf, ' ', _OPT_FIX_WIDTH);
                    p = buf + 2;

                    if (opt->sopt != '\0') {
                        *p++ = '-'; *p++ = opt->sopt;
                        *p++ = ','; *p++ = ' ';
                    } else {
                        p += 4;
                    }

                    if (opt->lopt != nullptr) {
                        *p++ = '-'; *p++ = '-';
                        p = strcopy(p, opt->lopt);
                    }

                    // 选项描述内容存在或者选项有参数
                    if (opt->desc != nullptr || opt->flag == arg_option::HasArg) {
                        std::fwrite(buf, 1, _OPT_FIX_WIDTH, stdout);

                        if (opt->desc != nullptr)
                            std::fwrite(opt->desc, 1, strlen(opt->desc), stdout);

                        if (opt->flag == arg_option::HasArg && opt->def) {
                            std::fwrite("(default: \x1b[33m", 1, 15, stdout);
                            std::fwrite(opt->def, 1, strlen(opt->def), stdout);
                            std::fwrite("\x1b[0m)", 1, 5, stdout);
                        }
                    } else {
                        std::fwrite(buf, 1, p - buf, stdout);
                    }

                    std::putc('\n', stdout);
                }
            }
        }

        // 解析命令行
        bool parse(int argc, char* const argv[]) {
            // 初始化val为空
            for (arg_option *opt = this->opts; opt->lopt != nullptr; ++opt)
                opt->val = nullptr;

            // 生成短选项解析所需要的字符串
            char optstring[256];
            gen_opt_string(optstring, this->opts);

            // 生成长选项结构
            size_t optsLen = get_opts_len(this->opts);
            option longOpts[optsLen + 1];
            gen_option(longOpts, this->opts);

            int long_idx = 0, finded;
            while ((finded = getopt_long(argc, argv, optstring, longOpts, &long_idx)) != -1) {
                // 匹配长选项
                if (finded == 0) {
                    arg_option *opt = &this->opts[long_idx];
                    opt->val = opt->flag == arg_option::NoArg ? (char*)true : optarg;
                } else {
                    arg_option* opt = opts;
                    for (; opt->lopt != nullptr; opt++) {
                        if (finded == opt->sopt) {
                            opt->val = opt->flag == arg_option::NoArg ? (char*)true : optarg;
                            break;
                        }
                    }

                    // 未找到匹配的短选项
                    if (opt->lopt == nullptr) return false;
                }
            }

            this->cmdarg_index = optind;
            return true;
        }

        bool is_set(const char* name) const {
            size_t len = std::strlen(name) + 1;
            for (arg_option* opt = this->opts; opt->lopt != nullptr; opt++)
                if (std::memcmp(name, opt->lopt, len) == 0)
                    return opt->val != nullptr;
            return false;
        }

        // 获取指定参数的值，如果是bool类型，则判断返回值是否为nullptr
        const char* get(const char* name) const {
            size_t len = std::strlen(name) + 1;
            for (arg_option* opt = this->opts; opt->lopt != nullptr; opt++)
                if (std::memcmp(name, opt->lopt, len) == 0)
                    return opt->val != nullptr ? opt->val : opt->def;
            return nullptr;
        }

        // 获取argv中第一个命令行参数的索引位置
        int get_cmdarg_index() const { return cmdarg_index; }

    private:
        static const size_t _OPT_FIX_WIDTH = 32;

        static inline char* strcopy(char* dst, const char* src) {
            while ((*dst++ = *src++));
            return --dst;
        }

        // 判断是否是选项，false表明为命令参数
        static inline bool is_option(arg_option::OptFlag flag) {
            return flag < arg_option::Optional;
        }

        // 获取选项数量（排除命令参数）
        static inline size_t get_opts_len(arg_option* opts) {
            size_t count = 0;
            for (arg_option* opt = opts; opt->lopt != nullptr; opt++)
                if (is_option(opt->flag)) ++count;
            return count;
        }

        // 创建getopt_long需要的短命令字符串
        static inline void gen_opt_string(char* dst, arg_option* opts) {
            char *p = dst;
            for (arg_option* opt = opts; opt->lopt != nullptr; opt++) {
                if (opt->sopt != '\0') {
                    *p++ = opt->sopt;
                    if (opt->flag == arg_option::HasArg)
                        *p++ = ':';
                }
            }
            *p = '\0';
        }

        // 创建getopt_long需要的长命令struct option数组
        static inline void gen_option(option* dst, arg_option* opts) {
            for (arg_option* opt = opts; opt->lopt != nullptr; opt++) {
                if (is_option(opt->flag)) {
                    dst->name = opt->lopt;
                    dst->has_arg = opt->flag;
                    dst->flag = opt->sopt != '\0' ? nullptr : &dst->val;
                    dst->val = opt->sopt != '\0' ? opt->sopt : 0;
                    ++dst;
                }
            }

            *dst = {.name = nullptr, .has_arg = 0, .flag = nullptr, .val = 0};
        }
    };


} // namespace klib
